package com.youlian.cloud.spring.binding;

import com.youlian.cloud.gateway.config.XmlGroupName;
import com.youlian.cloud.core.util.ObjectUtils;
import com.youlian.cloud.core.util.StringUtils;
import com.youlian.cloud.dao.BasicBaseDao;
import com.youlian.cloud.dao.IBaseDaoSupport;
import com.youlian.cloud.dao.IExampleDaoSupport;
import com.youlian.cloud.core.bean.PageModel;
import com.youlian.cloud.dao.row.JoinRowMapper;
import com.youlian.cloud.spring.base.EntityOperation;
import com.youlian.cloud.spring.common.TypeGroupModel;
import com.youlian.cloud.spring.common.utils.BeanUtils;
import com.youlian.cloud.spring.common.utils.DataUtils;
import com.youlian.cloud.spring.method.DefaultSQLBuilderSupport;
import com.youlian.cloud.spring.method.ISQLBuliderSupport;
import com.youlian.cloud.sql.ConfigurableFactory;
import com.youlian.cloud.sql.config.SqlCommandType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.RowMapper;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * dao层方法拦截根据指定的类型调用方法
 * <p>
 * 具体dao类执行的方法,根据方法名进行判断类型
 * </p>
 *
 * @author Liukx
 * @create 2018-01-03 11:36
 * @email liukx@elab-plus.com
 **/
public class DaoMethod {

    /**
     * 具体的执行接口
     */
    private Class<?> mapperInterface;

    /**
     * 执行的方法
     */
    private Method method;

    /**
     * DB层的SQL执行器
     */
    private BasicBaseDao basicBaseDao;

    /**
     * 父类的构造参数
     */
    private Class<?> supperGenricType;

    /**
     * 父类的接口
     */
    private String supperInterfaceName;

    /**
     * entity的操作对象
     */
    private EntityOperation entityOperation;

    /**
     * 配置文件对象,将sql.xml描述成对象
     */
    private ConfigurableFactory configurableFactory;

    /**
     * example类型 例如UserExample
     */
    private Class<?> exampleType;

    /**
     * 自动生成的SQL对象接口
     */
    private List<ISQLBuliderSupport> sqlBuliderSupportList = new ArrayList<ISQLBuliderSupport>();

    /**
     * 缓存一些反射的信息
     */
    private Map<String, Class<?>> methodReturnCacheMap = new ConcurrentHashMap<String, Class<?>>();

    /**
     * 默认的API接口描述
     */
    private final String defaultSupperInterface = IBaseDaoSupport.class.getName();

    /**
     * 默认的exaple的API接口对象
     */
    private final String defaultExampleInterface = IExampleDaoSupport.class.getName();

    /**
     * 当前类的sql文件中的groupName对象
     */
    private String xmlGroupName;

    private Logger logger = LoggerFactory.getLogger(DaoMethod.class);

    /**
     * 构建一个DaoMethod的方法对象
     *
     * @param mapperInterface     dao的接口類
     * @param method              方法信息
     * @param basicBaseDao        jdbc操作类
     * @param configurableFactory 配置工厂
     * @param <T>
     */
    public <T> DaoMethod(Class<?> mapperInterface, Method method, BasicBaseDao basicBaseDao, ConfigurableFactory configurableFactory) {
        // 接口信息
        this.mapperInterface = mapperInterface;
        // 方法信息
        this.method = method;
        // 具体操作类
        this.basicBaseDao = basicBaseDao;
        // 配置工厂
        this.configurableFactory = configurableFactory;
        //默认先将初始化的sql构建加入进来
        this.sqlBuliderSupportList.add(new DefaultSQLBuilderSupport());
        // 类的信息提取
        // 父类的接口类
        this.supperInterfaceName = mapperInterface.getGenericInterfaces()[0].getTypeName();
        // 泛型对象
        List<String> classGenricTypeString = BeanUtils.getClassGenricTypeString(this.supperInterfaceName);
        // 第一个db对象
        this.supperGenricType = BeanUtils.getClassByString(classGenricTypeString.get(0));
        // 第二个,案例对象
        this.exampleType = classGenricTypeString.size() > 1 ? BeanUtils.getClassByString(classGenricTypeString.get(1)) : null;
        // 实体操作类
        this.entityOperation = new EntityOperation(this.supperGenricType, "id");
        if (this.mapperInterface.isAnnotationPresent(XmlGroupName.class)) {
            this.xmlGroupName = this.mapperInterface.getAnnotation(XmlGroupName.class).value();
        }
    }

    /**
     * 具体执行sql的方法
     * 1. 判断sql的类型
     * 2. 根据类型判断调用的方法
     * 3.
     *
     * @param args
     * @return
     */
    public Object execute(Object[] args) throws Exception {
        int argsLength = args.length;
        String sqlSource = method.getName();
        String statementName = getSqlId();
//        // TODO 待优化 , 可以获取该方法的全称,然后去解析里面的参数名称
//        if (argsLength != 1) {
//            throw new IllegalArgumentException(sqlSource + " 方法 参数尽量保持在1个,可以是Map,可以是Object");
//        }
        Object arg = args[0];
        SqlCommandType sqlType = this.configurableFactory.getSqlConfigurableFactory().getSqlType(statementName);
        if (sqlType == null) {
            // 表示配置文件中没有找到,则会从父类接口去获取
            if (!StringUtils.isEmpty(this.supperInterfaceName)) {
                if (this.supperInterfaceName.startsWith(defaultSupperInterface)) {
                    // 如果是其他增删改类型的方法
                    if (createSql(statementName)) {
                        return execute(args);
                    }
                }

                if (this.defaultExampleInterface.startsWith(defaultExampleInterface)) {
                    // 如果是查找类型的方法
                    if ("find".equals(method.getName())) {
                        return findMethodInvoke(arg);
                    }
                }

            }
            // 抛出异常
            throw new Exception(" 找不到[" + statementName + "]对应的处理sql");
        } else {
            String sql = this.configurableFactory.getSqlConfigurableFactory().getSql(statementName);
            if (sqlType == SqlCommandType.INSERT) {
                if (arg instanceof List) {
                    return this.basicBaseDao.batchOperation(sql, (List) arg);
                } else {
                    return this.basicBaseDao.insert(sql, arg);
                }
            } else if (sqlType == SqlCommandType.UPDATE || sqlType == SqlCommandType.DELETE) {
                if (arg instanceof List) {
                    return this.basicBaseDao.batchOperation(sql, (List) arg);
                }
                return this.basicBaseDao.update(sql, arg);
            } else {
                if (sqlType != SqlCommandType.SELECT) {
                    System.out.println("语句类型有误!");
                    return null;
                }
                return selectInvokeMehtod(sql, args);
            }
        }

    }

    /**
     * 获取sql文件中的对应sql编号
     *
     * @return
     */
    private String getSqlId() {
        String statementName;
        if (StringUtils.isEmpty(this.xmlGroupName)) {
            statementName = mapperInterface.getName() + "." + method.getName();
        } else {
            statementName = this.xmlGroupName + "." + method.getName();
        }
        return statementName;
    }

    /**
     * 执行特殊的find的方法处理过程
     *
     * @param arg
     * @return
     */
    private Object findMethodInvoke(Object arg) {
        List oredCriteriaList = (List) BeanUtils.invokegetGetterMethod(arg, "oredCriteria");
        List<Object> paramsList = new LinkedList<Object>();
        StringBuffer sb = new StringBuffer();
        sb.append("select " + entityOperation.allColumn + " from " + entityOperation.tableName + " ");

        if (oredCriteriaList.size() > 0) {
            for (int i = 0; i < oredCriteriaList.size(); i++) {
                Object o = oredCriteriaList.get(i);
                List criteriaList = (List) BeanUtils.invokegetGetterMethod(o, "criteria");
                boolean flag = true;
                for (int j = 0; j < criteriaList.size(); j++) {
                    if (flag) {
                        sb.append(" where ");
                        flag = false;
                    } else {
                        sb.append(" and ");
                    }
                    Object o2 = criteriaList.get(j);
                    // key 条件
                    String condition = (String) BeanUtils.invokegetGetterMethod(o2, "condition");
                    // 对应的值
                    Object valueObject = BeanUtils.invokegetGetterMethod(o2, "value");
                    Object secondValue = BeanUtils.invokegetGetterMethod(o2, "secondValue");
                    // 如果是在集合的话需要单独处理  比如 in
                    if (valueObject instanceof List) {
                        List valueObject1 = (List) valueObject;
                        sb.append(condition + " ( ");
                        flag = false;
                        for (int k = 0; k < valueObject1.size(); k++) {
                            if (flag) {
                                sb.append(" , ");
                            }
                            Object o1 = valueObject1.get(i);
                            paramsList.add(o1);
                            sb.append(" ? ");
                            flag = true;
                        }
                        sb.append(" )");
                    } else {

                        sb.append(condition);

                        // is null , is not null
                        if (!ObjectUtils.isEmpty(valueObject)) {
                            paramsList.add(valueObject);
                            sb.append(" ? ");
                        }

                        // between 的情况
                        if (!ObjectUtils.isEmpty(secondValue)) {
                            sb.append(" and ? ");
                            paramsList.add(secondValue);
                        }
                    }
                }
            }
        }
        Object[] objects = paramsList.toArray();
        logger.debug(" SQL - " + sb.toString() + " \n " + Arrays.toString(objects));
        return this.basicBaseDao.getJdbcTemplate().query(sb.toString(), objects, entityOperation.rowMapper);
    }


    /**
     * 删除第一个and
     *
     * @param sql
     * @return
     */
    private String removeFirstAnd(String sql) {
        if (StringUtils.isEmpty(sql)) {
            return sql;
        }
        return sql.trim().toLowerCase().replaceAll("^\\s*and", "") + " ";
    }


    /**
     * 查询执行方法
     *
     * @param sql
     * @param args 参数列表
     * @return
     */
    private Object selectInvokeMehtod(String sql, Object[] args) throws Exception {
        Class<?> returnType = this.method.getReturnType();
        boolean returnsMany = (returnType == List.class);
        boolean returnsMap = (returnType == Map.class);
        boolean returnPage = (returnType == PageModel.class);
        TypeGroupModel paramsByType = DataUtils.getParamsByType(args);
        Object arg = args[0];
        // 是否是返回多个对象,例如List
        if (returnsMany) {
            Class typeClass = getCacheMethodReturnClass();
            if (typeClass == Map.class) {
                return this.basicBaseDao.executeQueryForListMap(sql, arg);
            } else {
                RowMapper rowMapper = paramsByType.getRowMapper();
                if (rowMapper == null) {
                    return this.basicBaseDao.executeQueryList(sql, arg, typeClass);
                } else {
                    return this.basicBaseDao.executeQueryMapper(sql, arg, rowMapper);
                }
            }
        }
        // 返回是Map的
        else if (returnsMap) {
            return this.basicBaseDao.executeQueryMap(sql, arg);
        }
        // 返回是分页对象的
        else if (returnPage) {
            arg = paramsByType.getObj().length == 0 ? paramsByType.getMap() : paramsByType.getObj()[0];
            Class typeClass = getCacheMethodReturnClass();
            PageModel pageModel = paramsByType.getPageModel();

            if (pageModel == null) {
                throw new Exception(" PageModel is not null !!!");
            }

            String sqlCount = "select count(1) from ( " + sql + " ) a";
            int count = this.basicBaseDao.executeQueryCount(sqlCount, arg);
            pageModel.setRowTotal(count);
            String sqlLimit = " " + sql + " " + pageModel.getOrderby() + "  limit " + (pageModel.getCount() - 1) * pageModel.getPageSize() + "," + pageModel.getPageSize();
            List list = null;
            if (typeClass == Map.class) {
                list = this.basicBaseDao.executeQueryForListMap(sqlLimit, arg);
            } else {
                list = this.basicBaseDao.executeQueryList(sqlLimit, arg, typeClass);
            }
            pageModel.setResultSet(list);
            return pageModel;
        }
        // 返回是对象的
        else {
            if (returnType == Object.class) {
                returnType = supperGenricType;
            }
            JoinRowMapper<?> joinRowMapper = JoinRowMapper.newInstance(basicBaseDao, this.configurableFactory, returnType);
            return this.basicBaseDao.executeQueryObject(sql, arg, joinRowMapper);
        }
    }


    /**
     * 获取方法返回值
     *
     * @return
     */
    private Class getCacheMethodReturnClass() {
        String methodName = this.method.getName();
        Class typeClass = methodReturnCacheMap.get(methodName);
        if (typeClass == null) {
            Type type = this.method.getGenericReturnType();
            typeClass = BeanUtils.getClassGenricType(type.getTypeName());
            // 可能是父类inteface的方法的情况
            if (typeClass == null) {
                typeClass = this.supperGenricType == null ? Map.class : this.supperGenricType;
            } else {
                methodReturnCacheMap.put(methodName, typeClass);
            }
        }
        return typeClass;
    }

    /**
     * 创建sql的执行方法
     *
     * @param statementName
     * @return
     * @throws Exception
     */
    private boolean createSql(String statementName) throws Exception {
        List<ISQLBuliderSupport> sqlBuliderSupportList = getSqlBuliderSupportList();
        String sql = "";
        boolean isSQLNotNull = false;
        for (int i = 0; i < sqlBuliderSupportList.size(); i++) {
            // 执行每个sql构建器来匹配
            ISQLBuliderSupport sqlBuliderSupport = sqlBuliderSupportList.get(i);
            sql = sqlBuliderSupport.doCreateSQL(entityOperation, method);
            if (!StringUtils.isEmpty(sql)) {
                isSQLNotNull = true;
                break;
            }
        }

        // 注册到sql容器中
        this.configurableFactory.getSqlConfigurableFactory().registerSqlMap(statementName, sql);
        return isSQLNotNull;
    }


    public List<ISQLBuliderSupport> getSqlBuliderSupportList() {
        return sqlBuliderSupportList;
    }

    public void setSqlBuliderSupportList(List<ISQLBuliderSupport> sqlBuliderSupportList) {
        this.sqlBuliderSupportList = sqlBuliderSupportList;
    }
}
